package nl.saxion.security.synchronize;

import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.crypto.SealedObject;

import nl.saxion.security.encryption.TransformerAES;
import nl.saxion.security.synchronize.Code;
import nl.saxion.security.transfer.Transfer;


/**
 * Class for Synchronize
 *
 * @author EII7SCb
 *
 */
public class Synchronize extends Thread {
	
	protected boolean listening = true;
	protected boolean sync = false;
	protected boolean busySync = false;
	
	protected SealedObject sealedKey;
	protected byte[] password;
	
	protected Transfer transfer;
	protected TransformerAES transformer;
	
	protected ArrayList<FileIncHash> toSendFiles = new ArrayList<FileIncHash>();
	protected ArrayList<FileIncHash> toReceiveFiles = new ArrayList<FileIncHash>();
	
	protected ArrayList<FileIncHash> fileList;
	protected File directory = null;
	
	
	/**
	 * Class for synchronize
	 * @param transfer
	 * @param directory
	 * @param sealedKey
	 * @param pass
	 */
	public Synchronize(Transfer transfer, String directory, SealedObject sealedKey, byte[] pass)
	{
		this.transfer = transfer;
		this.directory = new File(directory);
		this.sealedKey = sealedKey;
		this.password = pass;
		transformer = new TransformerAES();
	}
	
	/**
	 * Set the password used for decrypting
	 * @param pass hash from password
	 */
	public void setPassword(byte[] pass)
	{
		this.password = pass;
	}
	
	/**
	 * Set directory for file sync
	 * @param aDirectory
	 */
	public void setDirectory(String aDirectory) 
	{
		this.directory = new File(aDirectory);
	}
	
	/**
	 * Returns unsealed key
	 * @return key
	 * @throws Exception
	 */
	protected byte[] getKey() throws Exception
	{
		try {
			return (byte[]) transformer.decryptObject(password, sealedKey);
		} catch (Exception e) {
			throw new Exception("Wrong password!");
		}
	}

	/**
	 * Start thread
	 */
	public void run()
	{
		try {
			startListening();
			listening();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Stop listening for incomming messages
	 */
	protected void stopListening()
	{
		listening =  false;
	}
	
	/**
	 * Start listening for incomming messages
	 * @throws Exception
	 */
	protected void startListening() throws Exception
	{
		listening =  true;
	}
	
	/**
	 * Returns if thread is listening
	 * @return
	 */
	protected boolean getListening()
	{
		return listening;
	}
	
	/**
	 * Stop 
	 * @throws IOException
	 */
	public void stopServer() throws IOException
	{
		listening =  false;
		transfer.comCon.close();
	}
	
	/**
	 * Start synchronizing if there is a connection
	 * @return syncPosible
	 */
	public boolean enableSync()
	{
		if(isConnected())
			sync = true;
		else
			return false;
		
		return true;
	}
	
	/**
	 * 
	 * @return
	 */
	protected boolean getSync()
	{
		return sync;
	}
	
	/**
	 * Check if thread is synchronizing
	 * @return
	 */
	public boolean busySync()
	{
		return busySync;
	}

	/**
	 * is there a connection between devices
	 * @return
	 */
	public boolean isConnected()
	{
		if (transfer.comCon != null && transfer.comCon.isConnected())
			return true;
		else
			return false;
	}
	
	public void startSynchronize() throws Exception {
		throw new Exception("ERROR not ready!");
	}
	
	/**
	 * Start communication server
	 * @throws Exception 
	 */
	public void listening() throws Exception
	{
		
		if(transfer.comCon == null || !transfer.comCon.isConnected())
			transfer.createComConnection();
		
		System.out.println("Connection");
		
		syncKey();
		
		while(getListening())
		{
			Thread.sleep(200);
			
			if(transfer.comCon.dataOnLine()){
				SealedObject o = (SealedObject) transfer.comCon.getObject();
				Code code = (Code)transformer.decryptObject(getKey(), o);

				System.out.println("Client	> " + code.toString() );

				switch(Code.valueOf(code.toString()))	{
					case START_SYNC:{
						startSynchronize();
					}
					break;
				}
			}
			
			if(getSync() && !busySync)
			{
				startSynchronize();
				busySync = true;
				sync= false;
			}
				
		} 	  
	}
	

	

	
	protected void syncKey() throws Exception
	{
	
	}
	
	/**
	 * returns all the files from the given directory {@link Synchronize} 
	 * @return found files 
	 */
	protected ArrayList<FileIncHash> getLocalFilesIncHash(){
		
		ArrayList<File> files = getLocalFiles();
		ArrayList<FileIncHash> returnFiles = new ArrayList<FileIncHash>();
		
		for(File file : files){
			try {
				returnFiles.add(new FileIncHash(file.getAbsolutePath()));
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return returnFiles;
	}
	
	/**
	 * return files from directory
	 * @return found files
	 */
	protected ArrayList<File> getLocalFiles(){
		
		ArrayList<File> PCFiles = new ArrayList<File>();
		
		if(directory.isDirectory())
		{
			PCFiles.addAll(scanDirectory(directory));
		}
		return PCFiles;
	}
	

	/**
	 * scan given directory for files
	 * @param directory
	 * @return found files
	 */
	private ArrayList<File> scanDirectory(File directory)
	{
		ArrayList<File> Files = new ArrayList<File>();
		
		for(File child : directory.listFiles())
		{
			if(child.isDirectory())
			{
			//	scanDirectory(child);
			} else {
				Files.add(child);
			}
		}
		
		return Files;
	}
	

	/**
	 * Send code to other device
	 * @param code
	 * @throws Exception
	 */
	protected void sendCode(Code code) throws Exception
	{
		if(!transfer.comCon.sendData(code.toString().getBytes()))
			throw new Exception("No communication connection!");
	}
	
	/**
	 * Send code encrypted to other device
	 * @param code
	 * @throws Exception
	 */
	protected void sendCodeEncrypt(Code code) throws Exception
	{
		if(!transfer.comCon.sendObject(transformer.encryptObject(getKey(),code)))
			throw new Exception("No communication connection!");
	}

	/**
	 * Check if give code match the code that is received
	 * @param code code to macht
	 * @throws IOException
	 * @throws Exception
	 */
	protected void waitConfirm(Code code) throws IOException, Exception
	{
		String message = new String(transfer.comCon.readData());
		if(!message.equals(code.toString())){
			sendCodeEncrypt(Code.ERROR);
			throw new Exception("Expected '"+ code.toString() +"' received instead '"+message+"'");
		}
	}
	
	/**
	 * Check if give code match the encrypted code that is received
	 * @param code
	 * @throws IOException
	 * @throws Exception
	 */
	protected void waitConfirmEncrypt(Code code) throws IOException, Exception
	{
		Code message = (Code) transformer.decryptObject(getKey(), (SealedObject)transfer.comCon.getObject());
		if(!message.toString().equals(code.toString())){
			sendCodeEncrypt(Code.ERROR);
			throw new Exception("Expected '"+ code.toString() +"' received instead '"+message+"'");
		}
	}
	
	
	/**
	 * Send Files to other device over dataconnection.
	 * @param localFilesToSend
	 * @throws Exception
	 */
	protected void sendFiles(ArrayList<FileIncHash> localFilesToSend) throws Exception
	{
		for (FileIncHash file : localFilesToSend)
		{	
			if(!file.isDelete()){
				transfer.sendFile(file);
				waitConfirmEncrypt(Code.FILERECEIVED);
				System.out.println("File send: "+ file.getName());
				transfer.dataCon.close();	
			}
		}
	}
	
	/**
	 * Receive files form other device according to the set 'toReceiveFiles'
	 * @throws Exception
	 */
	protected void receiveFiles() throws Exception
	{
		for (FileIncHash file : toReceiveFiles)
		{
			String location = directory.getPath()+ "/" + file.getName();

			if(!file.isDelete()){
				
				transfer.getFile(location);
				sendCodeEncrypt(Code.FILERECEIVED);

				FileIncHash newFile = new FileIncHash(location); 
				newFile.setEncryptedHash(file.getEncryptedHash());
				newFile.setDecryptedHash(file.getDecryptedHash());
				newFile.setLastModifiedDate(file.getLastModifiedDate());
				this.fileList.add(newFile);
				
				System.out.println("File received: "+ file.getName());
			}
			else{
				File localfile = new File(location);
				Iterator<FileIncHash> localfiles2 = fileList.iterator();
				
		        while(localfiles2.hasNext()){
		        	FileIncHash localfile2 = localfiles2.next();
		        	if(localfile2.getName().equals(localfile.getName()))
		        		localfiles2.remove();
		        }
		        localfile.delete();
			}	
		}
	}
	
	/**
	 * Check if there a files been delete.
	 * @param localFiles list of current available files
	 * @return Files that have been deleted
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	protected ArrayList<FileIncHash> checkForDels(ArrayList<FileIncHash> localFiles) throws NoSuchAlgorithmException, IOException {

		ArrayList<FileIncHash> delFiles = new ArrayList<FileIncHash>();
		ArrayList<FileIncHash> cloneFileList = new ArrayList<FileIncHash>();
		cloneFileList.addAll(fileList);

		Iterator<FileIncHash> it = cloneFileList.iterator();

        while(it.hasNext()){
        	FileIncHash file = it.next();

			for(FileIncHash localFile: localFiles)
			{
				if(localFile.compareFileNames(file)){
					it.remove();
					break;
				}
			}
        }

		for(File file: cloneFileList)
		{
			FileIncHash delFile;
			delFile = new FileIncHash(file.getAbsolutePath());
			delFile.setDel(true);
			delFiles.add(delFile);
		}
		return delFiles;
	}
	

	/**
	 * Add only the new/edit files to filelist
	 * @param fileList
	 */
	protected void compareFileList(ArrayList<FileIncHash> fileList2)
	{
		Iterator<FileIncHash> it = fileList.iterator();
		while(it.hasNext())
		{
			FileIncHash file = it.next();	
			Iterator<FileIncHash> it2 = fileList2.iterator();

	        while(it2.hasNext())
			{
				FileIncHash newFile = it2.next();
			
				if(file.getName().equals(newFile.getName()) )
				{
						if(file.getLastModiDate() < newFile.getLastModiDate())
						{
						file.setEncryptedHash(newFile.getEncryptedHash());
						file.setDecryptedHash(newFile.getDecryptedHash());
						file.setLastModified( newFile.getLastModiDate());
						}
						it2.remove();
				}
			}
		}
		fileList.addAll(fileList2);
	}

}
